#include "simplerenderer.h"

#include "assist.h"

#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>

#include <QOpenGLContext>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLShaderProgram>

#include <QVector>

SimpleRenderer::SimpleRenderer(QString modelDiskPath, QWindow *window)
    : Renderer(modelDiskPath, window)
{
}

void SimpleRenderer::initialize()
{
    qDebug() << "SimpleRenderer::initialize() for " << m_modelDiskPath;

    m_vao = new QOpenGLVertexArrayObject(this);

    if (!m_vao->create()) {
        qFatal("QOpenGLVertexArrayObject creation failed.");
    }

    QOpenGLVertexArrayObject::Binder binder(m_vao);
    Q_UNUSED(binder)

    initializeProgram();
    initializeBuffers();
}

void SimpleRenderer::doRender()
{
    glDepthMask(true);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_DEPTH_CLAMP);
    //glDisable(GL_DEPTH_TEST);

    glClearColor(0.0, 0.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    QOpenGLVertexArrayObject::Binder binder(m_vao);
    Q_UNUSED(binder)

    m_vao->bind();

    m_program->setUniformValue("offset", m_offset);
    m_program->setUniformValue("scale", m_scale);
    m_program->setUniformValue("rotation", m_rotation);


    m_program->setUniformValueArray("material_ka",
                                    m_materialKaData.data(),
                                    m_materialKaData.count());

    m_program->setUniformValueArray("material_kd",
                                    m_materialKdData.data(),
                                    m_materialKdData.count());

    m_program->setUniformValueArray("material_ks",
                                    m_materialKsData.data(),
                                    m_materialKsData.count());

    glDrawArrays(GL_TRIANGLES, 0, m_vertexCount);

    glDisable(GL_DEPTH_TEST);
    glDisable(GL_DEPTH_CLAMP);

}

void SimpleRenderer::initializeProgram()
{
    m_program = new QOpenGLShaderProgram(this);
    initializeProgramOrFail(m_program, ":/shaders/vertex_2.vert", ":/shaders/fragment_2.fsh");
   //   initializeProgramOrFail(m_program, ":/shaders/vertex_1.vert", ":/shaders/fragment_1.fsh");

    m_program->bind();
}

void SimpleRenderer::initializeBuffers()
{
    Assimp::Importer importer;
    const aiScene* scene = importer.ReadFile(m_modelDiskPath.toLatin1().data(),
                                             0
                                             | aiProcess_Triangulate
//                                             | aiProcess_FindInvalidData
                                             );

    if (!scene) {
        qDebug() << "[SimpleRenderer::initializeBuffers()]"
                 << " assimp importer failed to read the scene from file, error string: "
                 << importer.GetErrorString();
        qFatal("SimpleRenderer::initializeBuffers() failed to load the scene");
    }

    qDebug() << "[SimpleRenderer::initializeBuffers()]"
             << " scene read successfully, num meshes: " << scene->mNumMeshes;

    initializeVertexPosBuffer(scene);
    initializeVertexNormBuffer(scene);
    initializeVertexMaterialData(scene);
}

void SimpleRenderer::initializeVertexPosBuffer(const aiScene* scene)
{
    float xMin, xMax;
    float yMin, yMax;
    float zMin, zMax;

    QVector<float> data = vertexPosData(scene, xMin, xMax, yMin, yMax, zMin, zMax);

    Q_ASSERT(xMin <= xMax);
    Q_ASSERT(yMin <= yMax);
    Q_ASSERT(zMin <= zMax);

    m_vertexCount = data.count()/3;
    calculateScaleAndOffset(xMin, xMax, yMin, yMax, zMin, zMax);

    initializeBuffer(m_vertexPosBuffer, data, "vertex_pos");
}

void SimpleRenderer::initializeVertexNormBuffer(const aiScene *scene)
{
    QVector<float> data = vertexNormData(scene);
    initializeBuffer(m_vertexNormBuffer, data, "vertex_norm");
}

void SimpleRenderer::readMaterialData(const aiScene *scene)
{
    qDebug() << "Simple3DRenderer::readMaterialData()";

    if (scene->HasMaterials()) {
        qDebug() << "...this scene has materials, reading them";
//        m_materialKaData.push_back(QVector3D(0.3, 0.3, 0.4));

        for (unsigned i = 0; i < scene->mNumMaterials; i++) {
            qDebug() << "material #" << i << ":";

            aiMaterial* material = scene->mMaterials[i];
            //
            aiColor3D ambient(0.0f, 0.0f, 0.0f);
            material->Get(AI_MATKEY_COLOR_AMBIENT, ambient);

            QVector3D ka(ambient.r, ambient.g, ambient.b);
            m_materialKaData.push_back(ka);

            qDebug() << "...ka: " << ka;
            //
            aiColor3D diffuse(0.0f, 0.0f, 0.0f);
            material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse);

            QVector3D kd(diffuse.r, diffuse.g, diffuse.b);
            m_materialKdData.push_back(kd);

            qDebug() << "...kd: " << kd;
            //
            aiColor3D specular(0.0f, 0.0f, 0.0f);
            material->Get(AI_MATKEY_COLOR_SPECULAR, specular);

            QVector3D ks(specular.r, specular.g, specular.b);
            m_materialKsData.push_back(ks);

            qDebug() << "...ks: " << ks;
        }
    } else {
        qDebug() << "...this scene has no materials, setting up the default one";
        m_materialKaData.push_back(QVector3D(0.3, 0.3, 0.4));
    }}

void SimpleRenderer::initializeVertexMaterialData(const aiScene *scene )
{
    m_vertexMaterialData = vertexMaterialData(scene);

    qDebug() << "Simple3DRenderer::readVertexMaterialData() -- count: "
             << m_vertexMaterialData.count();

    readMaterialData(scene);
}

void SimpleRenderer::initializeBuffer(QOpenGLBuffer& buffer, QVector<float> data, QString attribute)
{
    buffer = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);

    if (!buffer.create())
        qFatal("[SimpleRenderer::initializeBuffer()] QOpenGLBuffer::create() failed.");

    buffer.setUsagePattern(QOpenGLBuffer::StaticDraw);

    if(!buffer.bind())
        qFatal("[SimpleRenderer::initializeBuffer()] QOpenGLBuffer::bind() failed.");

    buffer.allocate(data.data(), m_vertexCount*3*sizeof(float));

    //const  char* attributeName = attribute.toLatin1().constData();
    QByteArray ba = attribute.toLatin1();
    const char* attributeName = ba.constData();

    qDebug() << "Atribute name as a char * = <" << attributeName << ">";
    m_program->enableAttributeArray(attributeName);
    m_program->setAttributeBuffer(attributeName, GL_FLOAT, 0, 3);
}

QVector<float> SimpleRenderer::vertexMaterialData(const aiScene *scene)
{
    QVector<float> data;
    float defaultMaterialIndex = 0;

    bool materialsPresent = scene->HasMaterials();

    if (!materialsPresent) {
        qDebug() << "[Simple3DRenderer::vertexMaterialData()]"
                 << " the scene doesn't have materials, setting default one,"
                 << " no rendering will be done";
    }

    for (unsigned i = 0; i < scene->mNumMeshes; i++) {
//        qDebug() << "SimpleRenderer::vertexMaterialData() -- processing mesh #"
//                 << i << "...";

        const aiMesh* mesh = scene->mMeshes[i];
        float materialIndex = materialsPresent
                                ? float(mesh->mMaterialIndex): defaultMaterialIndex;

        qDebug() << "material for mesh #" << i
                 << ": " << materialIndex;

        for (unsigned j = 0; j < mesh->mNumFaces; j++) {
            const aiFace& face = mesh->mFaces[j];

//            qDebug() << "face #" << j;

            for (unsigned k = 0; k < face.mNumIndices; k++) {
                data.push_back(materialIndex);
            }
        }
    }

    return data;
}

void adjustMinMax(float& currentMin, float& currentMax, float value)
{
    if (value < currentMin)
        currentMin = value;
    if (value > currentMax)
        currentMax = value;
}

QVector<float> SimpleRenderer::vertexPosData(const aiScene* scene,
                                             float& xMin, float& xMax,
                                             float& yMin, float& yMax,
                                             float& zMin, float& zMax)
{
    QVector<float> data;

    for (unsigned i = 0; i < scene->mNumMeshes; i++) {
//        qDebug() << "SimpleRenderer::vertexPosData() -- processing mesh #"
//                 << i << "...";

        const aiMesh* mesh = scene->mMeshes[i];

        for (unsigned j = 0; j < mesh->mNumFaces; j++) {
            const aiFace& face = mesh->mFaces[j];

//            qDebug() << "face #" << j;

            for (unsigned k = 0; k < face.mNumIndices; k++) {
                unsigned index = face.mIndices[k];
                const aiVector3D& v = mesh->mVertices[index];

//                qDebug() << "vertex #" << index << " { " << v.x << ", " << v.y << ", " << v.z << " }";

                data.push_back(v.x);
                data.push_back(v.y);
                data.push_back(v.z);

                if (data.count() == 3) {
                    xMin = v.x; xMax = v.x;
                    yMin = v.y; yMax = v.y;
                    zMin = v.z; zMax = v.z;
                } else {
                    adjustMinMax(xMin, xMax, v.x);
                    adjustMinMax(yMin, yMax, v.y);
                    adjustMinMax(zMin, zMax, v.z);
                }
            }
        }
    }

    qDebug() << "total vertex count: " << data.count()/3;

    return data;
}

QVector<float> SimpleRenderer::vertexNormData(const aiScene *scene)
{
    QVector<float> data;

    for (unsigned i = 0; i < scene->mNumMeshes; i++) {
//        qDebug() << "SimpleRenderer::vertexNormData() -- processing mesh #"
//                 << i << "...";

        const aiMesh* mesh = scene->mMeshes[i];

        if (!mesh->HasNormals())
            qFatal("at least one of the meshes doesn't have normals, aborting");

        for (unsigned j = 0; j < mesh->mNumFaces; j++) {
            const aiFace& face = mesh->mFaces[j];

//            qDebug() << "face #" << j;

            for (unsigned k = 0; k < face.mNumIndices; k++) {
                unsigned index = face.mIndices[k];

                const aiVector3D& n = mesh->mNormals[index];

//                qDebug() << "normal for vertex #" << index
//                         << " { " << n.x << ", " << n.y << ", " << n.z << " }";

                data.push_back(n.x);
                data.push_back(n.y);
                data.push_back(n.z);
            }
        }
    }

    return data;
}

float maxOfThree(float a, float b, float c)
{
    return std::max(a, std::max(b, c));
}

void SimpleRenderer::calculateScaleAndOffset(float xMin, float xMax,
                                             float yMin, float yMax,
                                             float zMin, float zMax)
{
    qDebug() << "Model geometry info: ";
    qDebug() << "xMin: " << xMin << " xMax: " << xMax;
    qDebug() << "yMin: " << yMin << " yMax: " << yMax;
    qDebug() << "zMin: " << zMin << " zMax: " << zMax;

    m_offset = QVector3D(-(xMax + xMin)/2,
                         -(yMax + yMin)/2,
                         -(zMax + zMin)/2);

    m_scale = 1.0/maxOfThree(xMax - xMin, yMax - yMin, zMax - zMin);

    qDebug() << "calculated initial offset: " << m_offset;
    qDebug() << "calculated initial scale: " << m_scale;
}
